﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using Tomahawk.Resources.Reflection;

namespace Tomahawk.Resources.Serialization
{
    /// <summary>
    /// This is the main interface class for the SerialBoost subsystem
    /// </summary>
    public class SerialBoost
    {

        #region "Singleton"

        /// <summary>
        /// Singleton instance
        /// </summary>
        private static SerialBoost instance;

        /// <summary>
        /// Singleton instance public accesor
        /// </summary>
        public static SerialBoost Instance
        {
            get
            {
                return instance;
            }
        }

        /// <summary>
        /// Setting this flag to true, will disable the use of BootSerializer for all types
        /// </summary>
        public static bool ForceAllUseXmlSerializer;

        /// <summary>
        /// Static construtor
        /// </summary>
        static SerialBoost()
        {
            instance = new SerialBoost();
            ForceAllUseXmlSerializer = false;
        }

        #endregion

        /// <summary>
        /// This will hold all the IBoostSerializer classes found within the application assemblies
        /// </summary>
        private Dictionary<String, IBoostSerializer> serializersDictionary;

        /// <summary>
        /// Constructor
        /// </summary>
        public SerialBoost()
        {
            this.indexSerializers();
            this.initializeXmlSerialize();
        }

        /// <summary>
        /// Cause the class to initialize
        /// </summary>
        public void Touch()
        {
        }

        /// <summary>
        /// Iterate all assemblies looking for Boost Serializers
        /// </summary>
        private void indexSerializers()
        {
            //create the serializers dictionary
            this.serializersDictionary = new Dictionary<String, IBoostSerializer>();

            //traverse all assemblies
            for (int aIdx = 0; aIdx < AssemblyIndexer.Instance.Assemblies.Length; aIdx++)
            {
                Assembly currentAsm = AssemblyIndexer.Instance.Assemblies[aIdx];
                if ((currentAsm != null) && (currentAsm.FullName.EndsWith("BoostSerializers")))
                {
                    try
                    {
                        Type[] currentAsmTypes = currentAsm.GetTypes();

                        //traverse all types in the assembly
                        for (int tIdx = 0; tIdx < currentAsmTypes.Length; tIdx++)
                        {
                            //get the type and key interface
                            Type currentType = currentAsmTypes[tIdx];
#if !XBOX
                        Type bsInterface = currentType.GetInterface(typeof(IBoostSerializer).FullName);
#else
                            //manually check the interfaces (GetInterface(iName) not suportted on xbox??)
                            Type bsInterface = null;
                            Type[] interfaces = currentType.GetInterfaces();
                            for (int i = 0; i < interfaces.Length; i++)
                            {
                                if (interfaces[i] == typeof(IBoostSerializer))
                                { bsInterface = interfaces[i]; break; }
                            }
#endif
                            //if it implements the IBoostSerializer interface, instance it
                            if ((bsInterface != null) && (!currentType.IsAbstract))
                            {
                                IBoostSerializer boostSerializer = currentAsm.CreateInstance(currentType.FullName) as IBoostSerializer;
                                if (boostSerializer != null)
                                {
                                    //a serializer had been created, index it
                                    this.serializersDictionary.Add(boostSerializer.TypeName, boostSerializer);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        ex = ex;
                    }
                }
            }

        }

        /// <summary>
        /// Return a BoostSerializer for a type
        /// </summary>
        internal IBoostSerializer getBoostSerializer(Type type)
        {
            IBoostSerializer retVal = null;
            this.serializersDictionary.TryGetValue(type.FullName, out retVal);
            return retVal;
        }

        #region "XmlSerialize support"

        private Dictionary<Type, XmlSerializer> xmlSerializersDictionary;

        private void initializeXmlSerialize()
        {
            this.xmlSerializersDictionary = new Dictionary<Type, XmlSerializer>();
        }

        /// <summary>
        /// Return the right Xml serializer from the pool
        /// </summary>
        public XmlSerializer GetXmlSerializer(Type type)
        {
            XmlSerializer retVal = null;
            this.xmlSerializersDictionary.TryGetValue(type, out retVal);
            if (retVal == null)
            {
                retVal = new XmlSerializer(type);
                xmlSerializersDictionary.Add(type, retVal);
            }
            return retVal;
        }

        #endregion

        #region "Serialization"

        /// <summary>
        /// Serializes an object into a MemoryStream
        /// </summary>
        public MemoryStream Serialize(object obj2Serialize)
        {
            MemoryStream ms = new MemoryStream();

            //get the boost serializer for the type
            IBoostSerializer bs = null;
            if (!ForceAllUseXmlSerializer && 
                (this.serializersDictionary.TryGetValue(obj2Serialize.GetType().FullName, out bs)))
            {
                XmlWriter xw = XmlWriter.Create(ms);
                bs.Serialize(obj2Serialize, xw);
                xw.Close();
            }
            else
            {
                XmlSerializer xmlSerialize = this.GetXmlSerializer(obj2Serialize.GetType());
                
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.ConformanceLevel = ConformanceLevel.Fragment;
                settings.OmitXmlDeclaration = true;

                XmlWriter xw = XmlWriter.Create(ms, settings);
                xmlSerialize.Serialize(xw, obj2Serialize);
                xw.Close();
            }
            return ms;
        }

        public void SerializeToWriter(object obj2Serialize, XmlWriter xmlW)
        {
            //get the boost serializer for the type
            IBoostSerializer bs = null;
            if (!SerialBoost.ForceAllUseXmlSerializer &&
                this.serializersDictionary.TryGetValue(obj2Serialize.GetType().FullName, out bs))
            {
                bs.Serialize(obj2Serialize, xmlW);
            }
            else
            {
                XmlSerializer xmlSerialize = this.GetXmlSerializer(obj2Serialize.GetType());
                xmlSerialize.Serialize(xmlW, obj2Serialize);
            }
        }

        public object Deserialize(Type objectType, byte[] data)
        {
            object retVal = null;

            //Create a stream
            MemoryStream ms = new MemoryStream(data);
            XmlReader xr = XmlReader.Create(ms);
            retVal = this.Deserialize(objectType, xr);
            xr.Close();
            ms.Close();
            return retVal;
        }

        public object Deserialize(Type objectType, string filePath)
        {
            object retVal = null;
            FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            XmlReader xr = XmlReader.Create(fs);
            retVal = this.Deserialize(objectType, xr);
            xr.Close();
            fs.Close();
            return retVal;
        }

        public object Deserialize(Type objectType, XmlReader xmlR)
        {
            object retVal = null;
            IBoostSerializer bs = null;
            if (!SerialBoost.ForceAllUseXmlSerializer &&
                this.serializersDictionary.TryGetValue(objectType.FullName, out bs))
            {
                retVal = bs.Deserialize(xmlR);
            }
            else
            {
                XmlSerializer xmlSerialize = this.GetXmlSerializer(objectType);
                retVal = xmlSerialize.Deserialize(xmlR);
            }
            return retVal;
        }

        /// <summary>
        /// Create an empty object, without applying an archetype
        /// </summary>
        public object CreateInstance(Type objectType)
        {
            object retVal = null;
            IBoostSerializer bs = null;
            if (!SerialBoost.ForceAllUseXmlSerializer &&
                this.serializersDictionary.TryGetValue(objectType.FullName, out bs))
            {
                retVal = bs.CreateInstance();
            }
            else
            {
                retVal = objectType.Assembly.CreateInstance(objectType.FullName);
            }
            return retVal;
        }

        #endregion
    }

}
